package com.it.annotaion;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zq
 * @create 2019-12-06
 */


@WebServlet(name = "dispatcherServlet", urlPatterns = "*.do", loadOnStartup = 1,
        initParams = {@WebInitParam(name = "base-package", value = "com.it")})
public class DispatcherServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    /**
     * 扫描包
     */
    private String basePackage = "";
    /**
     * 基包下面所有的带包路径权限定类名
     */
    private List<String> packageNames = new ArrayList<>();
    /**
     * 注解实例化 格式为注解上的名称：注解实例化对象
     */
    private Map<String, Object> instanceMap = new HashMap<>();
    /**
     * 包路径权限定类名称：注解上的名称
     */
    private Map<String, String> nameMap = new HashMap<>();
    /**
     * Url地址和方法的映射关系：注解上的名称
     */
    private Map<String, Method> urlMethodMap = new HashMap<>();

    /**
     * Method和权限定类名的映射关系，用于通过Method找到该方法的对象利用反射执行
     */
    private Map<Method, String> methodPackageMap = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求URI,eg：/user/hello
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String path = uri.replaceAll(contextPath, "");
        //提取出 URL，通过 URL 映射到Method 上，然后通过反射的方式进行调用即可。

        System.out.println("uri:" + uri);
        System.out.println("contentPath:" +  contextPath);
        System.out.println("path"+path);

        Method method = urlMethodMap.get(path);
        if(method!=null){
            //通过方法获取方法所在的包路径
            String packageName = methodPackageMap.get(method);
            //通过包路径获取注解上的名称
            String controllerName = nameMap.get(packageName);
            //通过注解名称获取对应的实例对象
            Object object = instanceMap.get(controllerName);
            method.setAccessible(true);
            try {
//                进行调用
                method.invoke(object,req,resp);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 初始化
     * 1、扫描基包下的类 得到信息A
     * 2、对于@controller @service @Repository 注解 需要拿到对应名称 并初始化他们修饰的类 形参映射关系
     * 3、 扫描类中的字段，如果发现有@Qualifier 的话需要完成注入
     * 4、扫描@RequestMapping 完成URL到某一Controller 的某一个方法的映射关系
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("开始初始化");
        String basePackage = config.getInitParameter("base-package");

        try {
            // 扫描 包
            scanPackage(basePackage);
            // 把带有注解的类 实例化 放入Map中  key 为注解上的名称
            instance(packageNames);
            // 实现依赖注入
            assemble();
            // 请求映射
            handleUrlMethodMap();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }


    }

    /**
     * 通过初始化参数直接将需要扫描的基包路径传入
     *
     * @param basePackage 基包路径
     */
    public void scanPackage(String basePackage) {
        URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        File basePackageFile = new File(url.getPath().replace("%20", ""));
        System.out.println("scan:" + basePackageFile);
        // 获取路径下所有文件
        File[] files = basePackageFile.listFiles();
        for (File file : files) {
            // 目录递归扫描
            if (file.isDirectory()) {
                scanPackage(basePackage + "." + file.getName());
            } else if (file.isFile()) {
                // 去掉 Controller.class 中的 .class
                System.out.println(">>>" + file.getName() + "====" + file.getName().split("\\.")[0]);
                packageNames.add(basePackage +"." + file.getName().split("\\.")[0]);
            }
        }
    }

    public void instance(List<String> packageNames) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (packageNames.size() < 1) {
            return;
        }
        for (String packageName : packageNames) {
//            根据包路径名实例化对象
            Class<?> clazz = Class.forName(packageName);
            // Controller 注解处理
            if (clazz.isAnnotationPresent(Controller.class)) {
                Controller controller = clazz.getAnnotation(Controller.class);
                String controllerName = controller.value();

                instanceMap.put(controllerName, clazz.newInstance());
                nameMap.put(packageName, controllerName);

                System.out.println("Controller :" + packageName + ", value :" + controllerName);
            } else if (clazz.isAnnotationPresent(Service.class)) {
                // Service 注解
                Service service = clazz.getAnnotation(Service.class);
                String serviceName = service.value();

                instanceMap.put(serviceName, clazz.newInstance());
                nameMap.put(packageName, serviceName);

                System.out.println("Controller :" + packageName + ", value :" + serviceName);
            } else if (clazz.isAnnotationPresent(Dao.class)) {
                //Dao注解处理
                Dao dao = clazz.getAnnotation(Dao.class);
                String daoName = dao.value();

                instanceMap.put(daoName, clazz.newInstance());
                nameMap.put(packageName, daoName);

                System.out.println("Dao :" + packageName + ", value :" + daoName);
            } else if (clazz.isAnnotationPresent(Repository.class)) {
                //Repository注解处理
                Repository repository = clazz.getAnnotation(Repository.class);
                String repositoryName = repository.value();

                instanceMap.put(repositoryName, clazz.newInstance());
                nameMap.put(packageName, repositoryName);
                System.out.println("Repository :" + packageName + ", value :" + repositoryName);
            }
        }

    }

    /**
     * 属性注解赋值
     */

    public void assemble() throws IllegalAccessException {
        for (Map.Entry<String, Object> instenceEntry : instanceMap.entrySet()) {
            // 得到类的属性
            Field[] fields = instenceEntry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                // 判断是否 有qualifier 注解
                if (field.isAnnotationPresent(Qualifier.class)) {
                    Qualifier qualifier = field.getAnnotation(Qualifier.class);
                    String value = qualifier.value();

                    // 可访问
                    field.setAccessible(true);
                    // 实现注入
                    field.set(instenceEntry.getValue(), instanceMap.get(value));
                    System.out.println("==========" + field);
                }
            }
        }
    }

    /**
     * 注解赋值
     */
    private void handleUrlMethodMap() throws ClassNotFoundException {
        if (packageNames.size() < 1) {
            return;
        }

        for (String packageName : packageNames) {
            Class<?> clazz = Class.forName(packageName);

            if (clazz.isAnnotationPresent(Controller.class)) {
                Controller con = clazz.getAnnotation(Controller.class);
                StringBuilder baseUrl = new StringBuilder();


                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping mapping = clazz.getAnnotation(RequestMapping.class);
                    baseUrl.append(mapping.value());
                }

                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                        StringBuilder url = new StringBuilder(baseUrl.toString());

                        url.append(annotation.value()) ;
                        url.append(".do");

                        System.out.println("===>>>>   url:" + url.toString());
                        urlMethodMap.put(url.toString(), method);
                        methodPackageMap.put(method, packageName);
                    }
                }
            }
        }

    }


}
